﻿using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using DeepZoomPivotConstructor.VisualLib;
using DeepZoomPivotConstructor.VisualLib.Hotspots;
using DeepZoomPivotConstructor.VisualLib.Utilities;
using IndicoInterface.SimpleAgendaDataModel;
using RenderEngineInterfaces;

namespace IndicoRender
{
    /// <summary>
    /// Given a string that is actually an existing file, attempt to render
    /// the existing file.
    /// </summary>
    [Export(typeof(IRenderItem))]
    [RenderInfo(typeof(Meeting))]
    public class RenderIndicoMeeting : IRenderItem
    {

        /// <summary>
        /// So we can do a render or two...
        /// </summary>
        [Import]
        private IRenderEngine RenderMe { get; set; }

        /// <summary>
        /// Helps with laying out everyone!
        /// </summary>
        [Import]
        private ILayoutHelper LayMeOut { get; set; }

        /// <summary>
        /// Do the rendering - we actually just create a file info and then get
        /// someone to try to render that. The "new" operator is fast enough that
        /// we don't need to take a hit on the effort of creating a task.
        /// [We also know the path exists as a file on the OS as we've checked
        /// it below - if that changes out from under us... well... :-)]
        /// </summary>
        /// <param name="what"></param>
        /// <returns></returns>
        public Task<Visual> Render(object what)
        {
            return Task<Visual>.Factory.StartNew(() => RenderMeetingNow(what as Meeting));
        }

        /// <summary>
        /// If we should be using a different temp directory than the one that is there by default... :-)
        /// </summary>
        [Import("RenderingTempDir", AllowDefault = true)]
#pragma warning disable 0649
        private DirectoryInfo RenderingTempDir;
#pragma warning restore 0649

        /// <summary>
        /// Returns a visual representing this meeting
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public Visual RenderMeetingNow(Meeting source)
        {
            ///
            /// Simple cases...
            /// 

            if (source == null || (source.Sessions == null && source.MeetingTalks == null))
            {
                return null;
            }

            ///
            /// Render the extra material associated with the meeting
            /// 

            var meetingMaterial = RenderExtraMeetingMaterial(source.MeetingTalks);

            ///
            /// Great! Render the sessions! Since we might just have material and no sessions, protect!!
            /// 

            Task<Visual>[] renderingSessions;
            if (source.Sessions != null)
            {
                renderingSessions = (from s in source.Sessions
                                     let rs = RenderMe.RenderToFuture(s)
                                     where rs != null
                                     select rs).ToArray();
            }
            else
            {
                renderingSessions = new Task<Visual>[0];
            }
            Task<Visual>.WaitAll(renderingSessions);

            var renderedSessionsAll = from s in renderingSessions
                                      let rend = s.Result
                                      where rend != null
                                      select rend;

            ///
            /// Line everything up and get it sized correctly!
            /// 

            var renderedSessions = (from s in new Visual[] { meetingMaterial }.Concat(renderedSessionsAll)
                                    where s != null
                                    select IndicoRenderUtilities.getSizeRight(s)).ToArray();

            ///
            /// Exactly what we do now depends on how many sessions we have.
            /// 0 and 1 are special cases. > 1 is a general caes.
            /// 

            if (renderedSessions.Count() == 0)
            {
                return null;
            }

            if (renderedSessions.Count() == 1)
            {
                Visual meeting = renderedSessions.First();
                meeting.Info = new Visual.VisualInfo();
                meeting.Info.Title = source.StartDate.ToString("yyyy-MM-dd") + " - " + source.Title;
                meeting.Info.Info = new string[] { IndicoRenderUtilities.TimeSpanToString(source.StartDate, source.EndDate) };
                return meeting;
            }

            ///
            /// Ok - here we have a "big" meeting. Do the standard thing with the layout
            /// 

            var renderedSessionsWithTitle = from rs in renderedSessions
                                            select new VisualTitleAndBackground(rs, RenderingTempDir)
                                            {
                                                {0.5, rs.Info.Title},
                                                {0.25, rs.Info.Info[0]}
                                            } as Visual;

            VisualHolder meetingHolder = new VisualHolder();
            var renderedSessionsNavigation = (from r in new VisualSeqenceWrapper(renderedSessionsWithTitle, meetingHolder)
                                              select r).ToArray();

            var meetingVisuals = LayMeOut.LayoutIn2By3Rows(renderedSessionsNavigation);
            meetingHolder.AddImage(meetingVisuals);
            VisualHotSpot vhs = meetingHolder.CreateVHS();
            vhs.Add(new HSActionCenterThere(renderedSessionsNavigation[0]) { KeysToActivate = HotSpotActionBase.ActivationKeys.KeyDownArrow });
            meetingHolder.AddImage(vhs);

            ///
            /// Done! Return that one big honking visual! :-)
            /// 

            meetingHolder.Info = new Visual.VisualInfo()
            {
                Title = source.StartDate.ToString("yyyy-MM-dd") + " - " + source.Title,
                Info = new string[] { IndicoRenderUtilities.TimeSpanToString(source.StartDate, source.EndDate) }
            };

            return meetingHolder;
        }

        /// <summary>
        /// Put all the associated meeting material in a single visual
        /// </summary>
        /// <param name="talks"></param>
        /// <returns></returns>
        private Visual RenderExtraMeetingMaterial(Talk[] talks)
        {
            ///
            /// Simple cases
            /// 

            if (talks == null || talks.Length == 0)
            {
                return null;
            }

            ///
            /// Render everything that needs to be rendered!
            /// 

            var allTasks = (from t in talks
                            let r = RenderMe.RenderToFuture(t)
                            where r != null
                            select r).ToArray();
            Task<Visual>.WaitAll(allTasks);

            var allGoodTalks = (from t in allTasks
                                where t.Result != null
                                select IndicoRenderUtilities.getSizeRight(t.Result)).ToArray();
            if (allGoodTalks.Length == 0)
            {
                return null;
            }

            ///
            /// We have a set of good talks to associate with this thing. Now we need to do the sequencing and layout.
            /// 

            var talksWithTitles = from rt in allGoodTalks
                                  select new VisualTitleAndBackground(rt, RenderingTempDir)
                                            {
                                                {0.25, "Extra material - " + rt.Info.Info[0]}
                                            } as Visual;

            VisualHolder meetingHolder = new VisualHolder();
            var renderedTalksWithLayout = (from r in new VisualSeqenceWrapper(talksWithTitles, meetingHolder)
                                           select r).ToArray();

            var meetingVisuals = LayMeOut.LayoutIn2By3Rows(renderedTalksWithLayout);
            meetingHolder.AddImage(meetingVisuals);
            VisualHotSpot vhs = meetingHolder.CreateVHS();
            vhs.Add(new HSActionCenterThere(renderedTalksWithLayout[0]) { KeysToActivate = HotSpotActionBase.ActivationKeys.KeyDownArrow });
            meetingHolder.AddImage(vhs);

            meetingHolder.Info = new Visual.VisualInfo()
            {
                Title = "Extra Material",
                Info = new string[] { "" }
            };

            return meetingHolder;
        }

        /// <summary>
        /// We can do any Indico Meeting that gets handed to us!
        /// </summary>
        /// <param name="what"></param>
        /// <returns></returns>
        public RenderPluginPriority RenderPriority(object what)
        {
            if (what == null)
                return RenderPluginPriority.RPPCantDoIt;

            return RenderPluginPriority.RPPGeneric;
        }
    }
}
